package com.biwin.module.orm.starter.repository.impl;

import com.biwin.common.api.basic.IBwPage;
import com.biwin.common.app.basic.BwPage;
import com.biwin.module.orm.api.dao.IBwHibernateDao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.springframework.jdbc.core.JdbcTemplate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;

/**
 * BiWin Framework basic dao implementation With Hibernate ORM Framework
 *
 * @author biwin
 * @since 0.0.1
 */
public class BwBasicHibernateDaoImpl<T> implements IBwHibernateDao<T> {

    protected final SessionFactory sessionFactory;

    protected final JdbcTemplate jdbcTemplate;

    public BwBasicHibernateDaoImpl(SessionFactory sessionFactory, JdbcTemplate jdbcTemplate) {
        this.sessionFactory = sessionFactory;
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 获取泛型 class 对象
     *
     * @return class
     */
    protected Class<T> getTClass() {
        Class<T> tClass =
                (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return tClass;
    }

    /**
     * 获取 session
     *
     * @return
     */
    protected Session getSession() {
        return this.sessionFactory.getCurrentSession();
    }

    private Query createQueryAndSetParameters(String hql, Map<String, Object> map, Object... values) {
        Query query = this.getSession().createQuery(hql);
        if (map != null && !map.isEmpty()) {
            Iterator i = map.keySet().iterator();
            while (i.hasNext()) {
                String key = (String) i.next();
                query.setParameter(key, map.get(key));
            }
        }
        if (values != null && values.length != 0) {
            for (int i = 0; i < values.length; ++i) {
                query.setParameter(i, values[i]);
            }
        }
        return query;
    }

    private Query setPagesAndRows(Query query, int page, int rows) {
        return query.setFirstResult(page).setMaxResults(rows);
    }

    public Long getCount(String hql, Map<String, Object> map, Object... params) {
        List<String> strs = Arrays.stream(hql.toLowerCase().split("from")).collect(Collectors.toList());
        int fromIndex = ((String) strs.get(0)).length();
        hql = hql.substring(fromIndex);
        String rowCountHql = hql.replace("fetch", "");
        int index = rowCountHql.toLowerCase().indexOf("order");
        if (index > 0) {
            rowCountHql = rowCountHql.substring(0, index);
        }
        String str = "select count(*) " + rowCountHql;
        return (Long) this.createQueryAndSetParameters(str, map, params).uniqueResult();
    }

    @Override
    public void delete(T o) {

    }

    @Override
    public void delete(String hql, Object... params) {
        this.createQueryAndSetParameters(hql, (Map) null, params).executeUpdate();
    }

    @Override
    public void update(T o) {
        this.getSession().update(o);
    }

    @Override
    public void update(String hql, Object... params) {
        this.createQueryAndSetParameters(hql, null, params).executeUpdate();
    }

    @Override
    public void saveOrUpdate(T o) {
        this.getSession().saveOrUpdate(o);
    }

    @Override
    public T get(Serializable id) {
        return this.getSession().get(this.getTClass(), id);
    }

    @Override
    public T get(String hql) {
        Query<T> q = this.createQueryAndSetParameters(hql, null);
        return q.uniqueResult();
    }

    @Override
    public T get(String hql, Map<String, Object> params) {
        Query<T> q = this.createQueryAndSetParameters(hql, params);
        return q.uniqueResult();
    }

    @Override
    public T get(String hql, Object... params) {
        Query<T> q = this.createQueryAndSetParameters(hql, null, params);
        return q.uniqueResult();
    }

    @Override
    public List<T> find(String hql) {
        return this.createQueryAndSetParameters(hql, null).list();
    }

    @Override
    public List<T> find(String hql, Object... params) {
        return this.createQueryAndSetParameters(hql, null, params).list();
    }

    @Override
    public List<T> find(String hql, Map<String, Object> params) {
        return this.createQueryAndSetParameters(hql, params).list();
    }

    @Override
    public List<T> find(String hql, int page, int rows) {
        return this.setPagesAndRows(createQueryAndSetParameters(hql, null), page, rows).list();
    }

    @Override
    public IBwPage<T> findForPage(String hql, int page, int rows, String gridId) {
        IBwPage<T> p = new BwPage<>();
        int totalCount = this.getCount(hql, null).intValue();
        p.setPageNo(page);
        p.setPageLimit(rows);
        p.setFrontTableId(gridId);
        p.setCount(totalCount);
        p.setTotalPage((totalCount + rows - 1) / rows);
        p.setDataList(this.setPagesAndRows(createQueryAndSetParameters(hql, null), page, rows).list());
        return p;
    }

    @Override
    public List<T> find(String hql, Map<String, Object> params, int page, int rows) {
        return this.createQueryAndSetParameters(hql, params).setFirstResult((page - 1) * rows).setMaxResults(rows).list();
    }

    @Override
    public IBwPage<T> findForPage(String hql, Map<String, Object> params, int page, int rows, String gridId) {
        IBwPage<T> p = new BwPage<>();
        int totalCount = this.getCount(hql, params).intValue();
        p.setPageNo(page);
        p.setPageLimit(rows);
        p.setFrontTableId(gridId);
        p.setCount(totalCount);
        p.setTotalPage((totalCount + rows - 1) / rows);
        p.setDataList(this.setPagesAndRows(createQueryAndSetParameters(hql, params), page, rows).list());
        return p;
    }

    @Override
    public List<T> find(String hql, int page, int rows, String gridId, Object... params) {
        return this.setPagesAndRows(this.createQueryAndSetParameters(hql, (Map) null, params), page, rows).list();
    }

    @Override
    public IBwPage<T> findForPage(String hql, int page, int rows, String gridId, Object... params) {
        IBwPage<T> p = new BwPage<>();
        int totalCount = this.getCount(hql, null, params).intValue();
        p.setPageNo(page);
        p.setPageLimit(rows);
        p.setFrontTableId(gridId);
        p.setCount(totalCount);
        p.setTotalPage((totalCount + rows - 1) / rows);
        p.setDataList(this.setPagesAndRows(createQueryAndSetParameters(hql, null, params), page, rows).list());
        return p;
    }

    @Override
    public List<T> findHql(String hql, Object... params) {
        return this.createQueryAndSetParameters(hql, (Map) null, params).list();
    }

    @Override
    public List<Object[]> findBySql(String sql, Object... params) {
        Query query = this.getSession().createSQLQuery(sql);
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; ++i) {
                query.setParameter(i, params[i]);
            }
        }
        return query.list();
    }

    @Override
    public void saveAll(Collection<T> entities) {
        Iterator i = entities.iterator();
        while (i.hasNext()) {
            T t = (T) i.next();
            this.save(t);
        }
    }

    @Override
    public void updateAll(Collection<T> entities) {
        Iterator i = entities.iterator();

        while (i.hasNext()) {
            T t = (T) i.next();
            this.update(t);
        }
    }

    @Override
    public void deleteAll(Collection<T> entities) {
        Iterator i = entities.iterator();
        while (i.hasNext()) {
            T t = (T) i.next();
            this.delete(t);
        }
    }

    @Override
    public Serializable save(T o) {
        return this.getSession().save(o);
    }

}
